PHP: Hypertext Preprocessor, The PHP Syntax, The PHP Command Line, Client/Server, REST
Autoloading, Leveraging PHP APIs, Dependency Management, Model View Controller
Databases
Sessions, Authentication, Writing Better Code, Testing, Awesome Projects, Embracing Open Source
$ sudo apt-get install php5-common libapache2-mod-php5 php5-cli
$ curl -s http://php-osx.liip.ch/install.sh | bash -s 7.0
$ curl -s http://php-osx.liip.ch/install.sh | bash -s 5.6
HipHop Virtual Machine for PHP, created by Facebook.
HHVM uses a just-in-time compilation approach to achieve superior performance.
4 scalar types: boolean, integer, float, string;
2 compound types: array, object;
2 special types: resource, null;
And 3 pseudo types: mixed, number, callback.
Note: most of these types have aliases. E.g. double for float.
Read more about the PHP primitive types: http://php.net/manual/en/language.types.intro.php.
// so, this is a PHP variable
$a = 5;
// compare value; return true
var_dump($a == 5);
// compare value (ignore type); return true
var_dump($a == '5');
// compare type/value (integer vs. integer); return true
var_dump($a === 5);
// compare type/value (integer vs. string); return false
var_dump($a === '5');
Read more about comparison operators: http://php.net/manual/en/language.operators.comparison.php.
The hash_equals()
function has been added in PHP 5.6 to compare two strings in constant time.
$a++; // or: ++$a;
$b--; // or: --$b;
$a && $b; // AND
$a || $b; // OR
! $a; // `true` if $a is not `true`
$a . 'foo'; // concatenation
2 ** 3 = 8 // exponentiation (PHP 5.6+)
But also:
$a = 'foo';
$a .= 'bar';
// $a => 'foobar'
$b = 0;
$b += 1; // $b = 1
$b -= 1; // $b = 0
$c = 2;
$c **= 3; // $c = 8
??// Fetches the value of $_GET['user'] and returns 'nobody'
// if it does not exist.
$username = $_GET['user'] ?? 'nobody';
// This is equivalent to:
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';
<=>Returns -1, 0 or 1 when $a is respectively less than, equal to, or
greater than $b:
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1
class Foo
{
}
Important: No class-level visibility in PHP.
abstract class AbstractFoo
{
abstract public function doSomething();
}
Creating an instance:
$foo = new Foo();
// $foo = new Foo;
// can also be done with a variable
$class = 'Foo';
$foo = new $class();
Getting the class name of an instance:
echo get_class($foo);
=> Foo
Useful keyword: instanceof
if ($foo instanceof Foo) {
// do something
}
new class {
public function foo() {
return 'foo';
}
}
Anonymous classes behave as traditional classes:
interface Logger {
public function log($msg);
}
$logger = new class implements Logger {
public function log($msg) {
// ...
}
};
$logger->log('Hello, Anonymous Class');
publicprotectedprivateAttribute visibility MUST be set.
Method visibility SHOULD be set.
Methods without any explicit visibility keyword are defined as public.
class Foo
{
const VALUE = 123;
// PHP 5.6+
const SENTENCE = 'The value of VALUE is ' . self::VALUE;
const ARRAY_OF_VALUES = ['a', 'b'];
/**
* @var int
*/
public static $count = 0;
/**
* @var Iterator
*/
public $iterator;
/**
* @var array
*/
protected $values = array();
/**
* @var string|null
*/
private $language = null;
}
class Foo
{
public function doSomething()
{
}
}
Works with classes, interfaces, arrays, callable, and Closure. You cannot
use scalar types such as int or string with PHP < 7.0:
public function doSomething(Foo $foo);
public function doSomething(Traversable $iterator);
public function doSomething(array $values);
public function doSomething(callable $callback);
public function doSomething(Closure $closure);
PHP 7 \o/
Works with int, float, string, and bool:
function sumOfInts(int ...$ints) {
return array_sum($ints);
}
function sumOfInts(int ...$ints) : int {
return array_sum($ints);
}
The -> operator is used to call methods on objects.
$foo = new Foo();
$foo->doSomething();
// >= PHP 5.4
(new Foo())->doSomething();
// can also be done with a variable
$method = 'doSomething';
$foo->$method();
$foo->{$method . 'Else'}();
// will call 'doSomethingElse()'; curly braces are required.
public function doSomething()
{
// method call
$this->doSomethingElse();
// parent method call (inheritance)
parent::doSomething();
// accessing a constant
self::VALUE;
// accessing a constant from another class
Bar::ANOTHER_VALUE;
// accessing an attribute
return $this->attribute;
}
Attributes/Methods can be defined as static:
class Foo
{
public static $value;
public static function doThings()
{
// accessing a static attribute
// don't forget the dollar sign!
self::$value;
}
}
Warning: the static keyword can also be used to define static
variables
and for late static
bindings.
This is different!
class A
{
public static function who() { echo __CLASS__; }
public static function testSelf()
{
self::who();
}
public static function testStatic()
{
static::who();
}
}
class B extends A
{
public static function who() { echo __CLASS__; }
}
B::testSelf();
// A
B::testStatic();
// B
$foo = new Foo();
// accessing the attribute from an instance
$foo::$value = 123;
// accessing the attribute directly from the class
echo Foo::$value;
=> 123
Read more: http://php.net/manual/en/language.oop5.static.php.
New operator ... as of PHP 5.6:
function sum(...$numbers)
{
return array_sum($numbers);
}
echo sum(1, 2);
// 3
$numbers = [ 2, 3 ];
echo sum(1, ...$numbers);
// 6
interface Fooable
{
const VALUE = 123;
// it's always public anyway
public function doSomething();
}
// Interface may extend several other interfaces.
// This is not possible with class though!
interface MyTraversable extends Traversable, Countable
{
}
// a class may implement several interfaces, but may extend only one class!
class Foo implements Fooable, MyTraversable {}
Namespaces prevent naming collisions with identifiers such as function, class, and interface names:
namespace Vendor\Model;
// ...
Or:
namespace MyNamespace {
// ...
}
PSR-0 describes a set of rules related to namespaces for autoloader interoperability:
\ns\package\Class_Name => vendor/ns/package/Class/Name.php
\ns\package_name\Class_Name => vendor/ns/package_name/Class/Name.php
Classes, functions, and constants have to be imported with the use
statement:
namespace My\Namespace;
// Pre PHP 7 code
use some\namespace\ClassA;
use some\namespace\ClassB;
use function some\namespace\fn_a;
use function some\namespace\fn_b;
// PHP 7+ code
use some\namespace\{ClassA, ClassB};
use function some\namespace\{fn_a, fn_b};
class MyClass
{
public function __construct(ClassA $a, ClassB $b) {
// ...
}
}
class KeywordSince PHP 5.5.0, class name resolution is possible via ::class.
namespace My\Namespace;
class ClassName
{
}
Assuming the class definition above, you can get the Fully Qualified Class Name (FQCN) by doing:
echo ClassName::class;
// My\namespace\ClassName
Read more about the
classkeyword: http://php.net/manual/en/language.oop5.basic.php.
Horizontal Inheritance FTW!
trait Hello trait World
{ {
public function sayHello() public function sayWorld()
{ {
echo 'Hello '; echo 'World';
} }
} }
class MyHelloWorld
{
use Hello, World;
}
$obj = new MyHelloWorld();
$obj->sayHello();
$obj->sayWorld();
Read more about traits: http://php.net/manual/en/language.oop5.traits.php.
An anonymous function, also known as lambda function, is a function defined, and possibly called, without being bound to an identifier.
$greet = function ($name) {
printf("Hello %s\n", $name);
};
$greet('World');
=> Hello World
Read more about anonymous functions: http://php.net/manual/en/functions.anonymous.php.
A closure is an anonymous function that owns a context.
$fibonacci = function ($n) use (&$fibonacci) {
if (0 === $n || 1 === $n) {
return $n;
}
return $fibonacci($n - 1) + $fibonacci($n - 2);
};
echo (int) $fibonacci(6);
=> 8
Read more about closures: http://php.net/manual/en/class.closure.php.
Starts with __.
Two useful methods:
__construct() { /* ... */ }
and:
__toString() { /* ... */ }
Other methods are not really useful but it's worth knowing them (__get(), __set()).
Read more about magic methods: http://php.net/manual/en/language.oop5.magic.php.
A generator function looks just like a normal function, except that instead of returning a value, a generator yields as many values as it needs to.
The heart of a generator function is the yield keyword.
Read more about generators:
No more Fatal Errors \o/
Many fatal and recoverable fatal errors have been converted to exceptions
inheriting from the new Error class, which itself implements the Throwable
interface, i.e. the new base interface all exceptions inherit.
PHP is an interpreted language, no need for a compiler.
You can try PHP using the command line:
$ php -r 'echo "Hello, World\n"'
Hello, World
Help available by running:
php -h
PHP also provides an interactive shell:
$ php -a
Interactive Shell
php > echo "Hello, World\n";
Hello, World
The command line is really useful, read more about command line options: http://php.net/manual/en/features.commandline.options.php.
Your new best friend is the linter:
$ php -l my/script.php
No syntax errors detected in my/script.php
A linter is a program that looks for problems in your code (syntax errors for instance).
Embedded web server:
$ php -S localhost:8000
Learn more about the built-in, command line web server: http://php.net/manual/en/features.commandline.webserver.php.
#!/usr/bin/env php
<?php
if (2 !== $argc) {
echo "Usage: php $argv[0] [name]\n";
exit(1);
}
$name = $argv[1];
echo "Hello, $name!\n";
Run the script:
$ ./hello.php
Usage: ./hello.php [name]
$ php hello.php
Usage: php hello.php [name]
$ php hello.php World
Hello, World!
A typical client/server request follows this pattern:
Server: Here is the resource at URI:
Content
For HTTP, a typical client is a web browser, and a server is a web server.
/bananas/joe: URI for banana "Joe"/bananas/henry: URI for banana "Henry"/bananas: collection of all available bananasRequest is made of:
Here is an example:
GET /my/simple/uri?with-query-string HTTP/1.1
Host: example.org
Content-Type: text/plain; charset=utf-8
Content-Length: 17
This is a content
An HTTP verb is an action to perform on a resource located at a given URI:
GET: retrieve a resource or a collection of resources;POST: create a new resource;PUT: update an existing resource or create a new resource at a
given URI;DELETE: delete a given resource;PATCH: partial update of a given resource.Important: this list is not exhaustive.
Response is made of:
Here is an example:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 76
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<h1>Hello world !</h1>
</body>
</html>
1xx Informational2xx Successful200 OK201 Created204 No Content3xx Redirections301 Moved Permanently302 Found304 Not Modified4xx Client Error400 Bad Request401 Unauthorized403 Forbidden404 Not Found405 Method Not Allowed406 Not Acceptable409 Conflict415 Unsupported Media Type451 Unavailable For Legal Reasons5xx Server Error500 Internal Server ErrorThere are two types of parameters, query string and request body.
If the request follows the URL Form Encoded format, you can access parameters through global variables:
$_GET;$_POST;$_REQUEST global variable.You can always use the following, but you need to parse them by yourself:
$_SERVER['QUERY_STRING'];$HTTP_RAW_POST_DATA
(deprecated,
do not use).Note: Don't use $_REQUEST, as there is a collision risk!
GET /my/simple/uri?a=1&id=2 HTTP/1.1
Host: example.org
Content-Type: text/plain; charset=utf-8
Content-Length: 14
b=3&city=paris
Will result in:
$_GET = [ "a" => 1, "id" => 2 ];
$_POST = [ "b" => 3, "city" => 'paris' ];
$_REQUEST = [ "a" => 1, "id" => 2, "b" => 3, "city" => 'paris' ];
$_SERVER['QUERY_STRING'] = "a=1&id=2";
$HTTP_RAW_POST_DATA = "b=3&city=paris";
Important: never trust user input, never!
REST is the underlying architectural principle of the web, formalized as a set of constraints, described in Roy Fielding's dissertation.
An API (i.e. a web service) that adheres to the principles of REST does not require the client to know anything about the structure of this API. Rather, the server needs to provide whatever information the client needs to interact with the service.
The key abstraction of information in REST is a resource. Any information that can be named can be a resource, and is identified by a Unified Resource Identifier (URI).
It heavily relies on the HTTP protocol: RFC 2616.
application/vnd.[XYZ];Accept / Content-Type headers.| Header | Description |
|---|---|
| `Content-Type` | HTTP message format |
| `Accept` | HTTP response format preference |
Hyper Media Types are MIME media types that contain native hyper-linking
semantics that induce application flow: application/hal+json,
application/collection+json, etc.
Content Type Negotiation is the principle of finding appropriate response formats based on client requirements.
No standardized algorithm available, even if the Apache
mod_negotiation
algorithm is documented. This also covers encoding (Accept-Encoding) and
language (Accept-Language) negotiation.
Accept: application/json, application/xml;q=0.9, text/html;q=0.8,
text/*;q=0.7, */*;q=0.5
| Priority | Mime Type |
|---|---|
| `q=1.0` | `application/json` |
| `q=0.9` | `application/xml` |
| `q=0.8` | `text/html` |
| `q=0.7` | `text/*` (ie. any text) |
| `q=0.5` | `*/*` (ie. any media type) |
HATEOAS stands for Hypermedia As The Engine Of Application State. It means that hypertext should be used to find your way through the API.
It is all about state transitions. Your application is just a big state machine. There should be a single endpoint for the resource, and all of the other actions you would need to undertake should be able to be discovered by inspecting that resource.
<?xml version="1.0" encoding="UTF-8"?>
<collection page="1" limit="10" pages="1">
<user id="123"></user>
<user id="456"></user>
<link rel="self" href="/api/users?page=1&limit=10" />
<link rel="first" href="/api/users?page=1&limit=10" />
<link rel="last" href="/api/users?page=1&limit=10" />
</collection>
Must read: Haters gonna HATEOAS.
PHP won't magically load your classes by itself.
You have to manually include the class declaration:
class Octopus
{
public function scream()
{
echo "o o O O ° °";
}
}
If you want to create a new Octopus, you will write the following code:
$paul = new Octopus();
$paul->scream();
As the class declaration isn't included, PHP raises a Fatal Error:
Fatal error: Class 'Octopus' not found in /path/to/file.php
require() WayInclude the class definition before instantiating it:
require __DIR__ . '../Model/Octopus.php';
$paul = new Octopus();
$paul->scream();
It works!
But, what happens when the class is included again, somewhere else?
// somewhere further in your application
require __DIR__ . '../Model/Octopus.php';
class Squid extends Octopus
{
}
PHP raises a Fatal Error:
Fatal error: Cannot redeclare class Octopus in /path/to/file.php
require_once() WayThe require_once() function is identical to require() except that PHP will
check whether the file has already been included:
require_once __DIR__ . '../Model/Octopus.php';
$paul = new Octopus();
$paul->scream();
And somewhere else:
// somewhere further in your application
require_once __DIR__ . '../Model/Octopus.php';
class Squid extends Octopus
{
}
It just works!
Multiple require_once() can turn into a nightmare when you deal with more than
a few files:
<?php
/**
* lib/Model/Location.php
*/
require_once __DIR__ . '/../../common.php';
require_once DOCROOT . '/lib/Model/ModelRepresentation.php';
require_once DOCROOT . '/lib/Model/User.php';
require_once DOCROOT . '/lib/Validator/Email.php';
require_once DOCROOT . '/lib/Validator/Username.php';
require_once DOCROOT . '/lib/Validator/Url.php';
class Location implements ModelRepresentation
{
/* ... */
}
PHP 5.2 and upper provides a usable autoloading API with performances close to
the use of require_once() thanks to the following functions:
__autoload()Main autoload callback.
spl_autoload_register()Register a new autoload callback.
spl_autoload_unregister()Unregister an autoload callback.
spl_autoload_functions()List all autoload methods.
__autoload()function __autoload($className)
{
require_once __DIR__ . DIRECTORY_SEPARATOR . $className . '.php';
}
spl_autoload_register()function my_autoload($className)
{
require_once __DIR__ . DIRECTORY_SEPARATOR . $className . '.php';
}
spl_autoload_register('my_autoload');
spl_autoload_unregister()spl_autoload_unregister('my_autoload');
new Foo();
The new algorithm in pseudo code:
1. Does the 'Foo' class exist?
=> Yes
Go on
=> No
Do you have registered autoload functions?
=> Yes
Call each function with 'Foo' as parameter
until the class gets included
=> No
Is there a `__autoload()` method?
=> Yes
Call `__autoload('Foo')`
2. Does the 'Foo' class exist?
=> Yes
Continue
=> No
Fatal Error
\Zend\Mail\Message
// => /path/to/project/lib/vendor/Zend/Mail/Message.php
Zend_Mail_Message
// => /path/to/project/lib/vendor/Zend/Mail/Message.php
Important: as of 2014-10-21 PSR-0 has been marked as deprecated.
Like PSR-0, but better:
Access properties as an array:
$tom = new MyObject();
$tom['name'] = 'Tom';
Allow the use of serialize() and unserialize().
Objects implementing JsonSerializable can customize their JSON representation
when encoded with json_encode().
Allow the use of foreach.
Read more: http://php.net/manual/en/reserved.interfaces.php.
Enable code introspection:
/** A comment */
class MyClass
{
public function hello() { printf("Hello %s", $this->getName()); }
protected function getName() { return 'foo'; }
}
$reflClass = new ReflectionClass('MyClass');
// access comments
var_dump($reflClass->getDocComment());
// string(16) "/** A comment */"
// get all methods
$reflClass->getMethods();
// get all public methods
$reflClass->getMethods(ReflectionMethod::IS_PUBLIC);
It is even possible to invoke private methods!
class MyClass
{
public function hello() { printf("Hello %s", $this->getName()); }
private function getName() { return 'foo'; }
}
$reflClass = new ReflectionClass('MyClass');
// access private method
$method = $reflClass->getMethod('getName');
$method->setAccessible(true);
$method->invoke(new MyClass());
// 'foo'
Read more: http://php.net/manual/en/book.reflection.php.
Provides a collection of classes and interfaces:
SplStack, SplQueue,
SplObjectStorage, etc.
LogicException, InvalidArgumentException, OutOfRangeException,
RuntimeException, etc.
class_parents(), spl_autoload_register(), spl_autoload_unregister(), etc.
Read more about the SPL: http://php.net/manual/en/book.spl.php.
The SplObserver interface is used alongside SplSubject to implement the
Observer Design Pattern.
class Subject implements SplSubject
{
/* ... */
}
class Observer implements SplObserver
{
/* ... */
}
$subject = new Subject();
$observer1 = new Observer();
$subject->attach($observer1);
$subject->notify();
Read more: http://php.net/manual/en/class.splobserver.php.
Those interfaces are never used as there is only one default channel for
the notify() method.
Symfony2 EventDispatcher component to the rescue!
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;
$dispatcher = new EventDispatcher();
$dispatcher->addListener('event_name', function (Event $event) {
// ...
});
$dispatcher->dispatch('event_name');
Read more: http://symfony.com/doc/master/components/event_dispatcher/.
try-catch block with multiple catch statements:
try {
// ...
} catch (RuntimeException $e) {
// do something
} catch (Exception $e) {
// do something else
}
Create your own exceptions:
class SomethingWentWrong extends RuntimeException
{
}
class ErrorWhileDoingSomething extends Exception implements ExceptionInterface
{
}
Name your named exceptions!
try-catch blocks also supports a finally block for code that should be run
regardless of whether an exception has been thrown or not:
try {
// ..
} catch (Exception $e) {
// do something
} finally {
// the code here will always be executed
}
The password hashing API provides an easy to use wrapper around crypt() to
make it easy to create and manage passwords in a secure manner, since PHP 5.5.0.
password_hash() and password_verify() are your new friends!
$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);
if (password_verify('bad-password', $passwordHash)) {
// Correct Password
} else {
// Wrong password
}
Read more about the Password Hashing API: http://php.net/manual/en/book.password.php.
A userland implementation exists for PHP >= 5.3.7: password_compat.
The phar extension provides a way to put entire PHP applications into a single file called a "phar" (PHP Archive) for easy distribution and installation.
But, the API is hard to use.
Solution? Box, a command line for simplifying the PHAR creation process.
Read more about PHAR:
There are a ton of PHP libraries, frameworks, and components to choose from. Most of them have different versions, and don't always work well together.
Composer is a tool for dependency management in PHP. It allows you to declare the dependent libraries your project needs and it will install them in your project for you.
A lot of awesome PHP libraries are compatible with Composer and listed on Packagist, the official repository for Composer-compatible PHP libraries.
$ curl -sS https://getcomposer.org/installer | php
This will download composer.phar (a PHP binary archive).
composer installCreate a composer.json file in your project's root directory:
{
"require": {
"willdurand/geocoder": "~2.0"
}
}
You can also require a library by using the require command:
$ php composer.phar require willdurand/geocoder
Run the following command to download and install the project dependencies into
a vendor directory:
$ php composer.phar install
Composer automatically generates a PSR-4 compliant and optimized autoloader for your entire application. Thanks to Composer, you don't have to take care about how to autoload classes/functions anymore.
Require the generated autoloader in your project as follows:
<?php
require 'vendor/autoload.php';
// your PHP code
Must read: Composer Primer.
Typical client request process in MVC architecture:
Model is the layer in charge of data interaction.
All data related business logic is embedded here. Using it should not require to understand internals.
Examples:
More on this next week!
More on this in a few minutes!
PHP is a templating language per se.
Never, ever, ever mix HTML and PHP codes or kittens will die: you have to separate the presentation from the business logic.
class PhpTemplateEngine implements TemplateEngine
{
private $templateDir;
public function __construct($templateDir)
{
$this->templateDir = $templateDir;
}
public function render($template, array $parameters = [])
{
extract($parameters);
ob_start();
include $this->templateDir . DIRECTORY_SEPARATOR . $template;
return ob_get_clean();
}
}
<!-- my_template.html -->
<p>Hello, <?php echo $name; ?>!</p>
Even better with PHP 5.4+:
<p>Hello, <?= $name ?>!</p>
$engine = new PhpTemplateEngine('/path/to/templates');
echo $engine->render('my_template.html', [
'name' => 'World',
]);
=> <p>Hello, World!</p>
Twig is a modern template engine for PHP. It takes care of escaping for you and much much more! Read more: http://twig.sensiolabs.org/.
{# my_template.html #}
<p>Hello, {{ name }}!</p>
$loader = new Twig_Loader_Filesystem('/path/to/templates');
$engine = new Twig_Environment($loader, [
'cache' => '/path/to/compilation_cache',
]);
echo $engine->render('my_template.html', [
'name' => 'World',
]);
=> <p>Hello, World!</p>
Glue between the Model and the View layers.
It should not contain any business logic.
class BananaController
{
public function __construct(
BananaRepository $repository,
TemplateEngine $engine
) {
$this->repository = $repository;
$this->engine = $engine;
}
public function listAction()
{
$bananas = $this->repository->findAll();
return $this->engine->render('list.html', [
'bananas' => $bananas,
]);
}
}
Routing is the process of binding URIs to controllers.
The simplest kind of routing, but also the hardest one to maintain:
web/
├ trees/
│ └ pineapple.php
â”” tree.php
Modern frameworks provide a routing component such as the Symfony2 Routing
component allowing to define routes in a centralized place, and easing URI
generation.
This require a single entry point: the Front Controller.
A controller that handles all requests for a web application:
This controller dispatches the request to the specialized controllers.
It is usually tied to URL rewriting.
In our context, a database is seen as a server hosting:
records;tables or collections;databases.Definitions and figures are part of the Catalog of Patterns of Enterprise Application Architecture created by Martin Fowler.
Don't forget his name! Read his books!
An object that acts as a Gateway to a single record (row) in a database. There is one instance per row.
// This is the implementation of `BananaGateway`
class Banana
{
private $id;
private $name;
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}
$con = new Connection('...');
$banana = new Banana();
$banana->setName('Super Banana');
// Save the banana
$banana->insert($con);
// Update it
$banana->setName('New name for my banana');
$banana->update($con);
// Delete it
$banana->delete($con);
public function insert(Connection $con)
{
// Prepared statement
$stmt = $this->con->prepare('INSERT INTO bananas VALUES (:name)');
$stmt->bindValue(':name', $name);
$stmt->execute();
// Set the id for this banana
//
// It becomes easy to know whether the banana is new or not,
// you just need to check if id is defined.
$this->id = $this->con->lastInsertId();
}
An object that acts as a Gateway to a database table. One instance handles all the rows in the table.
It's a Data Access Object.
$table = new BananaGateway(new Connection('...'));
// Insert a new record
$id = $table->insert('My favorite banana');
// Update it
$table->update($id, 'THE banana');
// Delete it
$table->delete($id);
A DAO implements the well-known Create Read Update Delete methods.
Read should not be a single method: Finders to the rescue!
class BananaGateway
{
private $con;
public function __construct(Connection $con)
{
$this->con = $con;
}
public function insert($name) {}
public function update($id, $name) {}
public function delete($id);
}
/**
* @param string $name The name of the banana you want to create
*
* @return int The id of the banana
*/
public function insert($name)
{
// Prepared statement
$stmt = $this->con->prepare('INSERT INTO bananas VALUES (:name)');
$stmt->bindValue(':name', $name);
$stmt->execute();
return $this->con->lastInsertId();
}
/**
* @param int $id The id of the banana to update
* @param string $name The new name of the banana
*
* @return bool Returns `true` on success, `false` otherwise
*/
public function update($id, $name)
{
$stmt = $this->con->prepare(<<<SQL
UPDATE bananas
SET name = :name
WHERE id = :id
SQL
);
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
return $stmt->execute();
}
/**
* @param int $id The id of the banana to delete
*
* @return bool Returns `true` on success, `false` otherwise
*/
public function delete($id)
{
$stmt = $this->con->prepare('DELETE FROM bananas WHERE id = :id');
$stmt->bindValue(':id', $id);
return $stmt->execute();
}
// Retrieve all bananas
$bananas = $table->findAll();
// Find bananas by name matching 'THE %'
$bananas = $table->findByName('THE %');
// Retrieve a given banana using its id
$banana = $table->find(123);
// Find one banana by name matching 'THE %'
$banana = $table->findOneByName('THE %');
Use the
__call()magic method to create magic finders: http://www.php.net/manual/en/language.oop5.overloading.php#object.call.
An object that wraps a row in a database table, encapsulates the database access, and adds domain logic on that data.
$con = new Connection('...');
$banana = new Banana();
$banana->setName('Another banana');
$banana->save($con);
// Call a method that is part of the domain logic
// What can a banana do anyway?
$banana->grow();
// Smart `save()` method
// use `isNew()` under the hood
$banana->save($con);
class Banana
{
private $height = 1;
public function grow()
{
$this->height++;
}
public function save(Connection $con)
{
if ($this->isNew()) {
// issue an INSERT query
} else {
// issue an UPDATE query
}
}
public function isNew()
{
// Yoda style
return null === $this->id;
}
}
A layer of Mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself.
Sort of "Man in the Middle".
class BananaMapper
{
private $con;
public function __construct(Connection $con)
{
$this->con = $con;
}
public function persist(Banana $banana)
{
// code to save the banana
}
public function remove(Banana $banana)
{
// code to delete the banana
}
}
$banana = new Banana();
$banana->setName('Fantastic Banana');
$con = new Connection('...');
$mapper = new BananaMapper($con);
$mapper->persist($banana);
$mapper->remove($banana);
Ensures that each object gets loaded only once by keeping every loaded object in a map. Looks up objects using the map when referring to them.
class Finder
{
private $identityMap = [];
public function find($id)
{
if (!isset($this->identityMap[$id])) {
// fetch the object for the given id
$this->identityMap[$id] = ...;
}
return $this->identityMap[$id];
}
}
A Data Access Layer (DAL) is a standard API to manipulate data, no matter which database server is used. A Data Source Name (DSN) can be used to determine which database vendor you are using.
A DSN in PHP looks like: <database>:host=<host>;dbname=<dbname> where:
<database> can be: mysql, sqlite, pgsql, etc;<host> is the IP address of the database server (e.g. localhost);<dbname> is your database name.$dsn = 'mysql:host=localhost;dbname=test';
$con = new PDO($dsn, $user, $password);
// Prepared statement
$stmt = $con->prepare($query);
$stmt->execute();
Looks like the Connection class you used before, right?
class Connection extends PDO
{
}
$con = new Connection($dsn, $user, $password);
Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior.
class Connection extends PDO
{
/**
* @param string $query
* @param array $parameters
*
* @return bool Returns `true` on success, `false` otherwise
*/
public function executeQuery($query, array $parameters = [])
{
$stmt = $this->prepare($query);
foreach ($parameters as $name => $value) {
$stmt->bindValue(':' . $name, $value);
}
return $stmt->execute();
}
}
/**
* @param int $id The id of the banana to update
* @param string $name The new name of the banana
*
* @return bool Returns `true` on success, `false` otherwise
*/
public function update($id, $name)
{
$query = 'UPDATE bananas SET name = :name WHERE id = :id';
return $this->con->executeQuery($query, [
'id' => $id,
'name' => $name,
]);
}
Introduces the notion of relations between objects:
An ORM is often considered as a tool that implements some design patterns seen above, and that eases relationships between objects.
$profile = $banana->getProfile();
$bananas = $bananaTree->getBananas();
$roles = [];
foreach ($banana->getBananaRoles() as $bananaRole) {
$roles[] = $bananaRole->getRole();
}
// Or, better:
$roles = $banana->getRoles();
An ORM that implements the Table Data Gateway and Row Data Gateway patterns, often seen as an Active Record approach.
Documentation: www.propelorm.org.
An ORM that implements the Data Mapper pattern.
Documentation: www.doctrine-project.org.
An object defined primarily by its identity is called an entity:
class Customer
{
private $id;
private $name;
public function __construct($id, Name $name)
{
$this->id = $id;
$this->name = $name;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
}
An object that represents a descriptive aspect of the domain with no conceptual identity is called a Value Object:
class Name
{
private $firstName;
private $lastName;
public function __construct($firstName, $lastName)
{
$this->firstName = $firstName;
$this->lastName = $lastName;
}
public function getFirstName()
{
return $this->firstName;
}
public function getLastName()
{
return $this->lastName;
}
}
A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection.
interface CustomerRepository
{
/**
* @return Customer
*/
public function find($customerId);
/**
* @return Customer[]
*/
public function findAll();
public function add(Customer $user);
public function remove(Customer $user);
}
Client objects construct query specifications declaratively and submit them to Repository for satisfaction.
Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes.
Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer.
Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.
The Specification pattern is a way to model business rules as individual
objects. The idea is that a question about an object, is answered by a
isSatisfiedBy() method:
interface CustomerSpecification
{
/**
* @return boolean
*/
public function isSatisfiedBy(Customer $customer);
}
class CustomerIsPremium implements CustomerSpecification
{
/**
* {@inheritDoc}
*/
public function isSatisfiedBy(Customer $customer)
{
// figure out if the customer is indeed premium,
// and return true or false.
}
}
A findSatisfying() method can be added to the CustomerRepository:
interface CustomerRepository
{
...
/**
* @return Customer[]
*/
public function findSatisfying(CustomerSpecification $specification);
}
$specification = new CustomerIsPremium();
$customers = $repository->findSatisfying($specification);
class OrSpecification implements CustomerSpecification
{
public function __construct(
CustomerSpecification $s1,
CustomerSpecification $s2
) {
$this->s1 = $s1;
$this->s2 = $s2;
}
public function isSatisfiedBy(Customer $c)
{
return $this->s1->isSatisfiedBy($c) || $this->s2->isSatisfiedBy($c);
}
}
class AndSpecification implements CustomerSpecification
{
...
public function isSatisfiedBy(Customer $c)
{
return $this->s1->isSatisfiedBy($c) && $this->s2->isSatisfiedBy($c);
}
}
class NotSpecification implements CustomerSpecification
{
public function __construct(CustomerSpecification $s)
{
$this->s = $s;
}
public function isSatisfiedBy(Customer $c)
{
return !$this->s->isSatisfiedBy($c);
}
}
// Find customers who have ordered exactly three times,
// but who are not premium customers (yet?)
$specification = new AndSpecification(
new CustomerHasOrderedThreeTimes(),
new NotSpecification(
new CustomerIsPremium()
)
);
$customers = $repository->findSatisfying($specification);
Reuse your specifications in your business layer:
class AwesomeOfferSender
{
private $specification;
public function __construct(CustomerIsPremium $specification)
{
$this->specification = $specification;
}
public function sendOffersTo(Customer $customer)
{
if ($this->specification->isSatisfiedBy($customer)) {
// send offers
}
}
}
Sessions are a way to preserve certain data across subsequent accesses.
// Initalize session
session_start();
// Regenerate identifier
session_regenerate_id();
// Assign "key" to `$value`
$_SESSION['key'] = $value;
// Retrieve "key"
$_SESSION['key'];
// Terminate session
session_destroy();
Session should be started before headers are sent! http://php.net/manual/en/book.session.php.
An example of PHP session configuration that is more secure:
; Helps mitigate XSS by telling the browser not to expose the cookie to
; client side scripting such as JavaScript
session.cookie_httponly = 1
; Prevents session fixation by making sure that PHP only uses cookies for
; sessions and disallow session ID passing as a GET parameter
session.use_only_cookies = 1
; Better entropy source
; Evades insufficient entropy vulnerabilities
session.entropy_file = "/dev/urandom"
; `session.entropy_length` might help too!
; Smaller exploitation window for XSS/CSRF/Clickjacking...
session.cookie_lifetime = 0
; Ensures session cookies are only sent over secure connections (it requires
; a valid SSL certificate)
; Related to OWASP 2013-A6-Sensitive Data Exposure
session.cookie_secure = 1
No Authentication/Security Layer, anyone can access everything.
The Security Layer, as seen before, has to intercept the process of converting a request into a response in order to perform some checks.
You need a way to hook into this process before invoking the controller: Interceptor Pattern to the rescue!
The Interceptor Pattern allows you to execute some code during the default application's lifecyle.
A way to implement this pattern is to use events. It is more or less like the Observer/Observable pattern.
The application notifies a set of listeners to an event. The listeners can register themselves to a particular event. An Event Dispatcher manages both the listeners, and the events.
Simple event dispatcher using a trait:
trait EventDispatcherTrait
{
private $events = [];
public function addListener($name, $callable)
{
$this->events[$name][] = $callable;
}
public function dispatch($name, array $arguments = [])
{
foreach ($this->events[$name] as $callable) {
call_user_func_array($callable, $arguments);
}
}
}
In order to intercept the process described before, you have to notify some
listeners once you enter in the process() method by dispatching the event:
class App
{
use EventDispatcherTrait;
...
private function process(Request $request, Route $route)
{
$this->dispatch('process.before', [ $request ]);
...
}
}
The listeners have to listen to this event:
$app->addListener('process.before', function (Request $request) {
// code to execute
});
Now that you can hook into the application's lifecycle, you can write a basic but powerful Firewall.
This firewall needs a whitelist of unsecured routes (i.e. routes that don't require the user to be authenticated) associated with their allowed HTTP methods:
$allowed = [
'/login' => [ Request::GET, Request::POST ],
'/locations' => [ Request::GET ],
];
The Firewall leverages the session to determine whether the user is authenticated or not:
session_start();
if (isset($_SESSION['is_authenticated'])
&& true === $_SESSION['is_authenticated']) {
return;
}
If authentication fails, the server should return a 401 status code.
$app->addListener('process.before', function(Request $req) use ($app) {
session_start();
$allowed = [
'/login' => [ Request::GET, Request::POST ],
];
if (isset($_SESSION['is_authenticated'])
&& true === $_SESSION['is_authenticated']) {
return;
}
foreach ($allowed as $pattern => $methods) {
if (preg_match(sprintf('#^%s$#', $pattern), $req->getUri())
&& in_array($req->getMethod(), $methods)) {
return;
}
}
switch ($req->guessBestFormat()) {
case 'json':
throw new HttpException(401);
}
return $app->redirect('/login');
});
$app->get('/login', function () use ($app) {
return $app->render('login.php');
});
$app->post('/login', function (Request $request) use ($app) {
$user = $request->getParameter('user');
$pass = $request->getParameter('password');
if ('will' === $user && 'will' === $pass) {
$_SESSION['is_authenticated'] = true;
return $app->redirect('/');
}
return $app->render('login.php', [ 'user' => $user ]);
});
$app->get('/logout', function (Request $request) use ($app) {
session_destroy();
return $app->redirect('/');
});
Useful for API authentication.
http://pretty-rfc.herokuapp.com/RFC2617
A framework helps you work better by structuring developments, and faster by reusing generic modules.
A framework facilitates long-term maintenance and scalability by complying with standard development rules.
Compliance with development standards also simplifies integrating and interfacing the application with the rest of the information system.
In other words, it works as a tool to make the development process easier and more productive.
Most of the time, a framework implements many kinds of design patterns.
Read more: Symfony explained to a Developer.
First of all:
Symfony is a reusable set of standalone, decoupled, and cohesive PHP components that solve common web development problems.
Then, based on these components:
Symfony is also a full-stack web framework.
Fabien Potencier, http://fabien.potencier.org/article/49/what-is-symfony2.
Symfony is built on powerful concepts:
It has been written by ~1502 developers.
Open Source, MIT licensed.
The Components implement common features needed to develop websites.
They are the foundation of the Symfony full-stack framework, but they can also be used standalone even if you don't use the framework as they don't have any mandatory dependencies.
There are ~30 components, including:
BrowserKit EventDispatcher OptionsResolver Templating
ClassLoader ExpressionLanguage Process Translation
Config Filesystem PropertyAccess VarDumper
Console Finder PropertyInfo Yaml
CssSelector Form Routing
Debug HttpFoundation Security
DependencyInjection HttpKernel Serializer
DomCrawler Intl Stopwatch
Say you want to play with YAML files, start by requiring the symfony/yaml
component into your composer.json file:
{
"require": {
"symfony/yaml": "~3.0"
}
}
Install it by running php composer.phar install, and use it:
require __DIR__ . '/vendor/autoload.php';
use Symfony\Component\Yaml\Yaml;
$yaml = Yaml::parse('/path/to/file.yml');
http://symfony.com/doc/current/components/yaml/introduction.html
The Symfony Framework accomplishes two distinct tasks:
The goal of the framework is to integrate many independent tools in order to provide a consistent experience for the developer. Even the framework itself is a Symfony bundle (i.e. a plugin) that can be configured or replaced entirely.
Symfony provides a powerful set of tools for rapidly developing web applications without imposing on your application.
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();
// the URI being requested (e.g. /about) minus any query parameters
$request->getPathInfo();
// the HTTP verb
$request->getMethod();
// GET variables
$request->query->get('foo');
// POST variables
$request->request->get('bar');
// SERVER variables
$request->server->get('HTTP_HOST');
// retrieve an HTTP request header, with normalized, lowercase keys
$request->headers->get('host');
use Symfony\Component\HttpFoundation\Response;
$response = new Response();
$response->setContent(<<<HTML
<html>
<body>
<h1>Hello world!</h1>
</body>
</html>
HTML
);
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/html');
// prints the HTTP headers followed by the content
$response->send();
// index.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
$request = Request::createFromGlobals();
$path = $request->getPathInfo();
if (in_array($path, ['', '/'])) {
$response = new Response('Welcome to the homepage.');
} elseif ('/hello' === $path) {
$response = new Response('hello, World!');
} else {
$response = new Response('Page not found.', 404);
}
$response->send();
It's all about transforming a Request into a Response:

The routing system determines which PHP function should be executed based on information from the request and routing configuration you've created.
# app/config/routing.yml
hello:
path: /hello
defaults: { _controller: AppBundle:Main:hello }
The AppBundle:Main:hello string is a short syntax that points to a
specific PHP method named helloAction() inside a class called
MainController.
This example uses YAML to define the routing configuration. Routing configuration can also be written in other formats such as XML or PHP.
In Symfony, a method in a controller is called an action. The convention is
to suffix each method with Action.
Also, each controller should be suffixed with Controller.
// src/AppBundle/Controller/MainController.php
namespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class MainController
{
public function helloAction()
{
return new Response('<h1>Hello, World!</h1>');
}
}
Recommended structure of a Symfony (3.x) project:
path/to/project/
app/
config/
Resources/
views/
bin/
console
src/
...
tests/
...
var/
cache/
logs/
sessions/
vendor/
...
web/
app.php
...
Each directory has its own purpose (and set of files):
app/ contains the application kernel, views, and the configuration;src/ contains your bundles;tests/ contains your tests;var/ contains files that change often (like in Unix systems);vendor/ contains your dependencies;web/ contains your front controllers and your assets.This is the central part of your application:
// app/AppKernel.php
use Symfony\Component\HttpKernel\Kernel;
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = [
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
// ...
];
if (in_array($this->getEnvironment(), ['dev', 'test'])) {
$bundles[] = // dev bundle;
}
return $bundles;
}
// ...
}
An application consists of a collection of "bundles" representing all of the features and capabilities of your application.
Each "bundle" can be customized via configuration files written in YAML, XML
or PHP.
By default, the main configuration file lives in the app/config/
directory and is called either config.yml, config.xml or config.php
depending on which format you prefer.
Symfony is all about configuring everything, and you can do pretty much everything you want. That's why people agreed on some conventions, but then again, a convention is just A way to do things, not THE way to do them.
# app/config/config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
framework:
secret: '%secret%'
router: { resource: '%kernel.root_dir%/config/routing.yml' }
# ...
# Twig Configuration
twig:
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
# ...
<!-- app/config/config.xml -->
<imports>
<import resource="parameters.yml"/>
<import resource="security.yml"/>
</imports>
<framework:config secret="%secret%">
<framework:router resource="%kernel.root_dir%/config/routing.xml"/>
<!-- ... -->
</framework:config>
<!-- Twig Configuration -->
<twig:config debug="%kernel.debug%" strict-variables="%kernel.debug%"/>
<!-- ... -->
$this->import('parameters.yml');
$this->import('security.yml');
$container->loadFromExtension('framework', [
'secret' => '%secret%',
'router' => [
'resource' => '%kernel.root_dir%/config/routing.php'
],
// ...
]);
// Twig Configuration
$container->loadFromExtension('twig', [
'debug' => '%kernel.debug%',
'strict_variables' => '%kernel.debug%',
]);
// ...
The main configuration MUST be written in YAML:
# app/config/config.yml
# ...
twig:
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
The routing definition MUST be written in YAML:
# app/config/routing.yml
hello:
path: /hello
defaults: { _controller: AppBundle:Main:hello }
The DI Container configuration MUST be written in XML:
<services>
<service id="acme_demo.controllers.main"
class="AppBundle\MainController" />
</services>
An application can run in various environments. The different environments share the same PHP code, but use different configuration.
A Symfony project generally uses three environments: dev, test and prod.
// web/app.php
// ...
$kernel = new AppKernel('prod', false);
The AppKernel class is responsible for actually loading the configuration file
of your choice:
// app/AppKernel.php
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(
__DIR__ . '/config/config_' . $this->getEnvironment() . '.yml'
);
}
A Bundle is a directory containing a set of files (PHP files, stylesheets, JavaScripts, images, ...) that implement a single feature (a blog, a forum, etc).
It should be reusable, so that you don't reinvent the wheel each time you need a common feature. In Symfony, (almost) everything lives inside a bundle.
In order to use a bundle in your application, you need to register it in the
AppKernel, using the registerBundles() method:
public function registerBundles()
{
$bundles = array(
// ...
new My\AwesomeBundle\MyAwesomeBundle(),
);
// ...
}
Recommended structure for a bundle:
XXX/...
DemoBundle/
DemoBundle.php
Controller/
Resources/
config/
doc/
index.rst
translations/
views/
public/
Tests/
LICENSE
The DemoBundle class is mandatory, and both LICENSE and
Resources/doc/index.rst files should be present.
The XXX directory(ies) reflects the namespace structure of the bundle.
| Type | Directory |
|---|---|
| Commands | Command/ |
| Controllers | Controller/ |
| Service Container Extensions | DependencyInjection/ |
| Event Listeners | EventListener/ |
| Configuration | Resources/config/ |
| Web Resources | Resources/public/ |
| Translation files | Resources/translations/ |
| Templates | Resources/views/ |
| Unit and Functional Tests | Tests/ |
A bundle has to extend the Symfony\Component\HttpKernel\Bundle\Bundle
class:
// src/Acme/MyFirstBundle/AcmeMyFirstBundle.php
namespace Acme\MyFirstBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AcmeMyFirstBundle extends Bundle
{
}
Then, you can register your bundle:
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
new Acme\MyFirstBundle\AcmeMyFirstBundle(),
);
return $bundles;
}
The web root directory is the home of all public and static files including images, stylesheets, and JavaScript files. It is also where each front controller lives:
// web/app.php
require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel('prod', false);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
The front controller file (app.php in this example) is the actual PHP file
that's executed when using a Symfony application and its job is to use a
Kernel class, AppKernel, to bootstrap the application, for a given
environment.
Creating a page is a three-step process involving a route, a controller, and (optionally) a template.
Each project contains just a few main directories: web/ (web assets and the
front controllers), app/ (configuration), src/ (your bundles), and vendor/
(third-party code).
Each feature in Symfony (including the Symfony framework core) is organized into a bundle, which is a structured set of files for that feature.
The configuration for each bundle lives in the Resources/config directory of the
bundle and can be specified in YAML, XML or PHP.
The global application configuration lives in the app/config/ directory.
Each environment is accessible via a different front controller (e.g. app.php
and app_dev.php) and loads a different configuration file.
A controller is a PHP function you create that takes information from the HTTP request and constructs and returns an HTTP response.
Every request handled by a Symfony project goes through the same lifecycle:
app.php or
app_dev.php) that bootstraps the application;_controller parameter from the
route;Response object;Response object are sent back to the
client.# app/config/routing.yml
homepage:
path: /
defaults: { _controller: AppBundle:Hello:index }
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController
{
public function indexAction()
{
return new Response('Home, Sweet Home!');
}
}
Every route must have a _controller parameter, which dictates which controller
should be executed when that route is matched.
This parameter uses a simple string pattern called the logical controller name.
The pattern has three parts, each separated by a colon: bundle:controller:action.
For example, a _controller value of AcmeBlogBundle:Blog:show means:
AcmeBlogBundle;BlogController;showAction.Notice that Symfony adds the string Controller to the class name (Blog =>
BlogController) and Action to the method name (show => showAction).
# src/AppBundle/Resources/config/routing.yml
app.hello_hello:
path: /hello/{name}
defaults: { _controller: AppBundle:Hello:hello }
requirements:
_method: GET
// src/AppBundle/Controller/HelloController.php
class HelloController
{
// ...
public function helloAction($name)
{
return new Response(sprintf('Home, Sweet %s!', $name));
}
}
For convenience, you can also have Symfony pass you the Request object as an argument to your controller:
use Symfony\Component\HttpFoundation\Request;
class HelloController
{
// ...
public function updateAction(Request $request)
{
// do something useful with $request
}
}
This is useful when you are working with forms.
Symfony comes with a base Controller class that assists with some of the most
common controller tasks and gives your controller class access to any resource
it might need:
use Symfony\Bundle\FrameworkBundle\Controller\Controller
class HelloController extends Controller
{
// ...
}
$this->redirect($this->generateUrl('homepage'));
return $this->render(
'hello/hello.html.twig', array('name' => $name)
);
The only requirement for a controller is to return a Response object.
Create a simple Response with a 200 status code:
use Symfony\Component\HttpFoundation\Response;
$response = new Response('Hello, ' . $name, 200);
Create a JSON response with a 200 status code:
$response = new Response(json_encode(array('name' => $name)));
$response->headers->set('Content-Type', 'application/json');
Or:
use Symfony\Component\HttpFoundation\JsonResponse;
$response = new JsonResponse(array('name' => $name));
The Symfony router lets you define URLs that you map to different areas of your application.
A route is a map from a URL path to a controller. Each route is named, and
maps a path to a _controller:
# app/config/routing.yml
homepage:
path: /
defaults: { _controller: AppBundle:Hello:index }
This route matches the homepage (/) and maps it to the
AppBundle:Hello:index controller.
blog:
path: /blog/{page}
defaults: { _controller: AcmeBlogBundle:Blog:index }
The path will match anything that looks like /blog/*.
Even better, the value matching the {page} placeholder will be available
inside your controller.
/blog will not match.
blog:
path: /blog/{page}
defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
By adding page to the defaults key, {page} is no longer required.
/blog will match this route and the value of the page parameter will be
set to 1. /blog/2 will also match, giving the page parameter a value of 2.
blog:
path: /blog/{page}
defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
requirements:
page: \d+
The \d+ requirement is a regular expression that says that the value of
the {page} parameter must be a digit (i.e. a number).
# src/AppBundle/Resources/config/routing.yml
app.hello_hello:
path: /hello/{name}
defaults: { _controller: AppBundle:Hello:hello }
methods: [ GET ]
# methods: [ GET, POST ]
All routes are loaded via a single configuration file, most of the time it will
be app/config/routing.yml.
In order to respect the "bundle" principle, the routing configuration should be located in the bundle itself, and you should just require it:
# app/config/routing.yml
appa:
resource: '@AppBundle/Resources/config/routing.yml'
# app/config/routing.yml
app:
resource: '@AppBundle/Resources/config/routing.yml'
prefix: /demo
The string /demo now be prepended to the path of each route loaded from
the new routing resource.
The Router is able to generate both relative and absolute URLs.
$router = $this->get('router');
$router->generate('app.hello_hello', [ 'name' => 'will'Â ]);
// /hello/will
$router->generate('app.hello_hello', [ 'name' => 'will' ], true);
// http://example.com/hello/will
$router->generate('app.hello_hello', [
'name' => 'will', 'some' => 'thing'
]);
// /hello/will?some=thing
Fast, Secure, Flexible.
<ul id="navigation">
<?php foreach ($navigation as $item): ?>
<li>
<a href="<?php echo $item->getHref() ?>">
<?php echo $item->getCaption() ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<ul id="navigation">
{% for item in navigation %}
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
{% endfor %}
</ul>
{{ ... }}: prints a variable or the result of an expression;{% ... %}: controls the logic of the template; it is used to execute for
loops and if statements, for example;{# ... #}: comments.{# array('name' => 'Fabien') #}
{{ name }}
{# array('user' => array('name' => 'Fabien')) #}
{{ user.name }}
{# force array lookup #}
{{ user['name'] }}
{# array('user' => new User('Fabien')) #}
{{ user.name }}
{{ user.getName }}
{# force method name lookup #}
{{ user.name() }}
{{ user.getName() }}
{# pass arguments to a method #}
{{ user.date('Y-m-d') }}
{% if user.isSuperAdmin() %}
...
{% elseif user.isMember() %}
...
{% else %}
...
{% endif %}
<ul>
{% for user in users if user.active %}
<li>{{ user.username }}</li>
{% else %}
<li>No users found</li>
{% endfor %}
</ul>
Filters are used to modify Twig variables.
You can use inline filters by using the | symbol:
{{ 'hello'|upper }}
But you can also use the block syntax:
{% filter upper %}
hello
{% endfilter %}
Filters can be parametrized:
{{ post.createdAt|date('Y-m-d') }}
The include tag is useful to include a template and return the rendered
content of that template into the current one:
{% include 'sidebar.html' %}
Given the following template:
{% for user in users %}
{% include "render_user.html" %}
{% endfor %}
with render_user.html:
<p>{{ user.username }}</p>
<p>William D.</p>
<p>Julien M.</p>
Let's define a base template, base.html, which defines a simple HTML skeleton:
{# app/Resources/views/base.html.twig #}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Test Application{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog">Blog</a></li>
</ul>
{% endblock %}
</div>
<div id="content">
{% block body %}{% endblock %}
</div>
</body>
</html>
The key to template inheritance is the {% extends %} tag.
A child template might look like this:
{# app/Resources/views/Blog/index.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}My cool blog posts{% endblock %}
{% block body %}
{% for entry in blog_entries %}
<h2>{{ entry.title }}</h2>
<p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}
If you need to get the content of a block from the parent template, you can
use the {{ parent() }} function.
By default, templates can live in two different locations:
app/Resources/views/: The applications views directory can contain
application-wide base templates (i.e. your application's layouts),
templates specific to your app as well as templates that override bundle
templates;path/to/bundle/Resources/views/: Each (public) bundle houses its templates in its
Resources/views directory (and subdirectories).Symfony uses a bundle:controller:template string syntax for templates.
You can skip the controller string: bundle::template. The template
file would live in Resources/views/.
You can also skip the bundle string. It refers to an application-wide base
template or layout. This means that the template is not located in any bundle,
but instead in the root app/Resources/views/ directory.
AcmeBlogBundle:Blog:index.html.twig
AcmeBlogBundle: (bundle) the template lives inside the AcmeBlogBundle (e.g.
src/Acme/BlogBundle);Blog: (controller) indicates that the template lives inside the Blog
subdirectory of Resources/views;index.html.twig: (template) the actual name of the file is index.html.twig.Assuming that the AcmeBlogBundle lives at src/Acme/BlogBundle, the final
path to the layout would be:
src/Acme/BlogBundle/Resources/views/Blog/index.html.twig
Once you use a third-party bundle, you'll likely need to override and customize one or more of its templates.
When the FooBarBundle:Bar:index.html.twig is rendered, Symfony actually
looks in two different locations for the template:
app/Resources/FooBarBundle/views/Bar/index.html.twig;src/Foo/BarBundle/Resources/views/Bar/index.html.twig.In order to override the bundle template, copy the index.html.twig template
from the bundle to: app/Resources/FooBarBundle/views/Bar/index.html.twig.
The core TwigBundle contains a number of different templates that can be
overridden by copying each from the Resources/views/ directory of the
TwigBundle to the app/Resources/TwigBundle/views/ directory.
public function listAction()
{
// ...
return $this->render('blog/index.html.twig', array(
'posts' => $posts,
));
}
$engine = $this->container->get('templating');
$content = $engine->render('blog/index.html.twig', array(
'posts' => $posts,
));
return new Response($content);
Assuming the following routing definition:
homepage:
path: /
defaults: { _controller: AppBundle:Hello:index }
acme_blog.post_show:
path: /posts/{slug}
defaults: { _controller: AcmeBlogBundle:Post:show }
You can create a relative URL using path():
<a href="{{ path('homepage') }}">Home</a>
You can create an absolute URL using url():
<a href="{{ url('homepage') }}">Home</a>
The second argument is used to pass parameters:
<a href="{{ path('acme_blog.post_show', {'slug': 'my-super-slug'}) }}">
<script src={{ asset('js/script.js') }}></script>
<link href="{{ asset('css/style.css') }}" rel="stylesheet">
<img src="{{ asset('images/logo.png') }}" alt="Symfony!" />
Cache busting is the process of forcing browsers or proxy servers to update their cache, for instance, JavaScript and CSS files or images.
# app/config/config.yml
framework:
# ...
templating: { engines: ['twig'], assets_version: v2 }
The asset_version parameter is used to bust the cache on assets by globally
adding a query parameter to all rendered asset paths:
/images/logo.png?v2
The FOSJsRoutingBundle allows you to expose your routing in your JavaScript code. That means you'll be able to generate URL with given parameters like you can do with the Router component provided by Symfony.
# app/config/routing.yml
my_route_to_expose:
path: /foo/{id}/bar
defaults: { _controller: FooBarBundle:Foo:bar }
options:
expose: true
According to the routing definition above, you can write the following JavaScript code to generate URLs:
Routing.generate('my_route_to_expose', { id: 10 });
// /foo/10/bar
Routing.generate('my_route_to_expose', { id: 10 }, true);
// http://example.org/foo/10/bar
app.security: the security context;app.user: the current user object;app.request: the request object;app.session: the session object;app.environment: the current environment (dev, prod, etc);app.debug: true if in debug mode. false otherwise.A Service is a generic term for any PHP object that performs a specific task.
A service is usually used globally, such as a database connection object or an object that delivers email messages.
In Symfony, services are often configured and retrieved from the service container.
An application that has many decoupled services is said to follow a Service-Oriented Architecture (SOA).
A Service Container, also known as a Dependency Injection Container (DIC), is a special object that manages the instantiation of services inside an application.
The service container takes care of lazily instantiating and injecting dependent services.
class Foo
{
private $bar;
private $debug;
public function __construct(Bar $bar = null, $debug = false)
{
$this->bar = $bar;
$this->debug = $debug;
}
}
The service definition for the class described above is:
<services>
<service id="foo" class="My\Bundle\Foo" />
</services>
This service is now available in the container, and you can access it by asking the service from the container:
$foo = $this->container->get('foo');
The service definition described before is not flexible enough. For instance,
$debug argument is never configured.
Parameters make defining services more organized and flexible:
<parameters>
<parameter key="my_bundle.foo.class">My\Bundle\Foo</parameter>
</parameters>
<services>
<service id="foo" class="%my_bundle.foo.class%">
<argument></argument> <!-- null -->
<argument>%kernel.debug%</argument>
</service>
</services>
In the definition above, kernel.debug is a parameter defined by the framework
itself. The foo service is now parametrized.
Also, it becomes easy to change the implementation of this service by simply
overriding the my_bundle.foo.class parameter.
As you may noticed, the Foo class takes an instance of Bar as first
argument. You can inject this instance in your foo service by
referencing the bar service:
<parameters>
<parameter key="my_bundle.foo.class">My\Bundle\Foo</parameter>
<parameter key="my_bundle.bar.class">My\Bundle\Bar</parameter>
</parameters>
<services>
<service id="bar" class="%my_bundle.bar.class%" />
<service id="foo" class="%my_bundle.foo.class%">
<argument type="service" id="bar" />
<argument>%kernel.debug%</argument>
</service>
</services>
<call method="setBar">
<argument type="service" id="bar" />
</call>
imports Way# app/config/config.yml
imports:
- { resource: "@AcmeDemoBundle/Resources/config/services.xml" }
A service container extension is a PHP class to accomplish two things:
An extension class should live in the DependencyInjection directory of your
bundle and its name should be constructed by replacing the Bundle suffix of
the Bundle class name with Extension.
// Acme/DemoBundle/DependencyInjection/AcmeDemoExtension.php
namespace Acme\DemoBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
class AcmeDemoExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader($container, new FileLocator(
__DIR__ . '/../Resources/config'
));
$loader->load('services.xml');
}
}
The presence of the previous class means that you can now define an
acme_demo configuration namespace in any configuration file:
# app/config/config.yml
acme_demo: ~
Take the following configuration:
acme_demo:
foo: fooValue
bar: barValue
The array passed to your load() method will look like this:
array(
array(
'foo' => 'fooValue',
'bar' => 'barValue',
)
)
The $configs argument is an array of arrays, not just a single flat array
of the configuration values.
It's your job to decide how these configurations should be merged together.
You might, for example, have later values override previous values or somehow merge them together:
public function load(array $configs, ContainerBuilder $container)
{
$config = array();
foreach ($configs as $subConfig) {
$config = array_merge($config, $subConfig);
}
// ... now use the flat $config array
}
// src/Acme/DemoBundle/DependencyInjection/Configuration.php
namespace Acme\DemoBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('acme_demo');
$rootNode
->children()
->scalarNode('my_type')->defaultValue('bar')->end()
->end();
return $treeBuilder;
}
}
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
// ...
}
The processConfiguration() method uses the configuration tree you've defined
in the Configuration class to validate, normalize and merge all of
the configuration arrays together.
Read more on How to expose a Semantic Configuration for a Bundle: http://symfony.com/doc/master/cookbook/bundles/extension.html.
In the service container, a tag implies that the service is meant to be used for a specific purpose.
<service id="my_bundle.twig.foo" class="My\Bundle\Twig\FooExtension">
<tag name="twig.extension" />
</service>
Twig finds all services tagged with twig.extension and automatically registers
them as extensions.
$ php bin/console debug:container
$ php bin/console debug:container foo
$ php bin/console
You can get help information:
$ php bin/console help cmd
$ php bin/console cmd --help
$ php bin/console cmd -h
You can get more verbose messages:
$ php bin/console cmd --verbose
$ php bin/console cmd -v [-vv] [-vvv]
You can suppress output:
$ php bin/console cmd --quiet
$ php bin/console cmd -q
assets
assets:install Installs bundles web assets under a public
web directory
cache
cache:clear Clears the cache
cache:warmup Warms up an empty cache
config
config:dump-reference Dumps default configuration for an extension
container
container:debug Displays current services for an application
debug
debug:container Displays current services for an application
debug:router Displays current routes for an application
router
router:match Helps debug routes by simulating a path info
match
server
server:run Runs PHP built-in web server
translation
translation:update Updates the translation file
lint
lint:twig Lints a template and outputs encountered
errors
Create a Command directory inside your bundle and create a php file suffixed
with Command.php for each command that you want to provide:
// src/AppBundle/Command/GreetCommand.php
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class GreetCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('demo:greet');
}
protected function execute(
InputInterface $input,
OutputInterface $output
) {
// code ...
}
}
Arguments are the strings, separated by spaces, that come after the command name itself. They are ordered, and can be optional or required.
protected function configure()
{
$this
// ...
->addArgument(
'name',
InputArgument::REQUIRED,
'Who do you want to greet?'
)
->addArgument(
'last_name',
InputArgument::OPTIONAL,
'Your last name?'
);
}
$input->getArgument('last_name');
Unlike arguments, options are not ordered, always optional, and can be setup to accept a value or simply as a boolean flag without a value.
protected function configure()
{
$this
// ...
->addOption(
'yell',
null,
InputOption::VALUE_NONE,
'If set, the task will yell in uppercase letters'
);
}
// php bin/console demo:greet --yell
if ($input->getOption('yell')) {
// ...
}
protected function configure()
{
$this
// ...
->addOption(
'iterations',
null,
InputOption::VALUE_REQUIRED,
'How many times should the message be printed?',
1
);
}
// php bin/console demo:greet --iterations=10
for ($i = 0; $i < $input->getOption('iterations'); $i++) {
}
protected function execute(
InputInterface $input,
OutputInterface $output
) {
$translator = $this->getContainer()->get('translator');
// ...
}
$command = $this->getApplication()->find('demo:greet');
$arguments = array(
'command' => 'demo:greet',
'name' => 'Fabien',
'yell' => true,
);
$returnCode = $command->run(new ArrayInput($arguments), $output);
public function newAction(Request $request)
{
$form = $this->createFormBuilder()
->add('name')
->add('bio', 'textarea')
->add('birthday', 'date')
->getForm();
return $this->render('default/new.html.twig', [
'form' => $form->createView(),
]);
}
In order to display the Form, you need to pass a special view object to the
View layer. It's achieved through the createView() method.
{# src/AppBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path('acme_demo.default_new') }}" method="post">
{{ form_widget(form) }}
<input type="submit" />
</form>
When initially loading the page in a browser, the request method is GET and the form is simply created and rendered;
When the user submits the form (i.e. the method is POST) with invalid data, the form is bound and then rendered, this time displaying all validation errors;
When the user submits the form with valid data, the form is bound and you have the opportunity to perform some actions before redirecting the user to some other page (e.g. a "success" page).
Redirecting a user after a successful form submission prevents the user from being able to hit "refresh" and re-post the data.
public function newAction(Request $request)
{
$form = $this->createFormBuilder()
->add('name')
->add('bio', 'textarea')
->add('birthday', 'date')
->getForm();
if ($form->handleRequest($request)->isValid()) {
$data = $form->getData();
// do something ...
return $this->redirect($this->generateUrl('success'));
}
// ...
}
Everything is a Type!
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('bio', 'textarea')
->add('birthday', 'date');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'data_class' => My\Person::class,
]);
}
public function getName()
{
return 'person';
}
}
public function newAction(Request $request)
{
$person = new Person();
$form = $this->createForm(PersonType::class, $person);
if ($form->handleRequest($request)->isValid()) {
$person->save(); // insert a new `person`
return $this->redirect($this->generateUrl('success'));
}
// ...
}
Placing the form logic into its own class means that the form can be easily reused elsewhere in your project.
This is the best way to create forms, but the choice is up to you!
processForm() Method (1/2)Saving or updating an object is pretty much the same thing. In order to avoid
code duplication, you can use a processForm() method that can be used in both
the newAction() and the updateAction():
/**
* Create a new Person
*/
public function newAction(Request $request)
{
return $this->processForm($request, new Person());
}
/**
* Update an existing Person
*/
public function updateAction(Request $request, $id)
{
$person = ...; // get a `Person` by its $id
return $this->processForm($request, $person);
}
processForm() Method (2/2)/**
* @param Request $request
* @param Person $person
*
* @return Response
*/
private function processForm(Request $request, Person $person)
{
$form = $this->createForm(PersonType::class, $person);
if ($form->handleRequest($request)->isValid()) {
$person->save();
return $this->redirect($this->generateUrl('success'));
}
return $this->render('default/new.html.twig', [
'form' => $form->createView(),
]);
}
CSRF is a method by which a malicious user attempts to make your legitimate users unknowingly submit data that they don't intend to submit. Fortunately, CSRF attacks can be prevented by using a CSRF token inside your forms.
CSRF protection works by adding a hidden field to your form, called _token
by default that contains a value that only you and your user knows.
This ensures that the user is submitting the given data. Symfony automatically validates the presence and accuracy of this token.
The _token field is a hidden field and will be automatically rendered if you
include the form_rest() function in your template, which ensures that all
un-rendered fields are output.
<form action="" method="post" {{ form_enctype(form) }}>
{{ form_errors(form) }}
{{ form_row(form.name) }}
{{ form_row(form.bio) }}
{{ form_row(form.birthday) }}
{{ form_rest(form) }}
<input type="submit" />
</form>
Read more: http://symfony.com/doc/master/book/forms.html#rendering-a-form-in-a-template.
form_enctype(form): if at least one field is a file upload field, this renders
the obligatory enctype="multipart/form-data";
form_errors(form): renders any errors global to the whole form (field-specific
errors are displayed next to each field);
form_row(form.name): renders the label, any errors, and the HTML form widget
for the given field inside, by default, a div element;
form_rest(form): renders any fields that have not yet been rendered. It's
usually a good idea to place a call to this helper at the bottom of each form.
This helper is also useful for taking advantage of the automatic CSRF Protection.
In the previous section, you learned how a form can be submitted with valid or invalid data. In Symfony, validation is applied to the underlying object.
In other words, the question isn't whether the "form" is valid, but whether the object is valid after the form has applied the submitted data to it.
Calling $form->isValid() is a shortcut that asks the object whether it has
valid data using a Validation layer.
Validation is done by adding a set of rules (called constraints) to a class.
This component is based on the JSR303 Bean Validation specification.
Given the following class:
namespace AppBundle\Entity;
class Author
{
public $name;
}
You can configure a set of constraints on it:
# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
properties:
name:
- NotBlank: ~
validator Service$author = new Author();
// ... do something to the $author object
$validator = $this->get('validator');
$errors = $validator->validate($author);
if (count($errors) > 0) {
// Ooops, errors!
} else {
// Everything is ok :-)
}
If the $name property is empty, you will see the following error message:
AppBundle\Author.name:
This value should not be blank
Most of the time, you won't interact directly with the validator service or need to worry about printing out the errors. You will rather use validation indirectly when handling submitted form data.
Constraints can be applied to a class property or a public getter method
(e.g. getFullName()). The first is the most common and easy to use, but the
second allows you to specify more complex validation rules.
Validating class properties is the most basic validation technique. Symfony allows you to validate private, protected or public properties.
# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
properties:
firstName:
- NotBlank: ~
Some constraints apply to the entire class being validated. For example, the
Callback constraint is a generic constraint that's applied to the class
itself.
Constraints can also be applied to the return value of a method. Symfony
allows you to add a constraint to any public method whose name starts with
get or is.
# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
getters:
passwordLegal:
- "False":
message: "The password cannot match your first name"
With the following code in the Author class:
public function isPasswordLegal()
{
return ($this->firstName !== $this->password);
}
In some cases, you will need to validate an object against only some of the constraints on that class.
You can organize each constraint into one or more validation groups, and then apply validation against just one group of constraints.
# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\User:
properties:
email:
- Email: { groups: [ registration ] }
password:
- NotBlank: { groups: [ registration ] }
- Length: { groups: [ registration ], min: 7 }
city:
- Length:
min: 2
With the configuration seen before, there are two validation groups:
To tell the validator to use a specific group, pass one or more group names as
the second argument to the validate() method:
$errors = $validator->validate($author, [ 'registration' ]);
If your object takes advantage of validation groups, you'll need to specify which validation group(s) your form should use:
$form = $this
->createFormBuilder($users, [
'validation_groups' => [ 'registration' ],
])
->add(...);
If you're creating form classes, then you'll need to add the following to
the setDefaultOptions() method:
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'validation_groups' => [ 'registration' ],
]);
}
use Symfony\Component\Validator\Constraints\Email;
$emailConstraint = new Email();
$emailConstraint->message = 'Invalid email address';
$errorList = $this->get('validator')->validateValue(
$email, $emailConstraint
);
if (0 !== count($errorList)) {
// this is *not* a valid email address
$errorMessage = $errorList[0]->getMessage();
}
By calling validateValue() on the validator, you can pass in a raw value and
the constraint object that you want to validate that value against.
The validateValue() method returns a ConstraintViolationList object, which
acts just like an array of errors.
Each error in the collection is a ConstraintViolation object, which holds the
error message on its getMessage() method.
The term internationalization (often abbreviated i18n) refers to the process of abstracting strings and other locale-specific pieces out of your application and into a layer where they can be translated and converted based on the user's locale (i.e. language and country).
The act of creating translation files is an important part of localization (often abbreviated l10n). It is the process of adapting a product or service to a particular language, culture, and desired local look-and-feel.
translator Service# messages.fr.yml
Symfony is great: J'aime Symfony
'Hello %name%': Bonjour %name%
When the following code is executed, Symfony will attempt to translate the
message Symfony is great based on the locale of the user:
echo $this->get('translator')->trans('Symfony is great');
Now, if the language of the user's locale is French (e.g. fr_FR or fr_BE),
the message will be translated into J'aime Symfony.
echo $this->get('translator')->trans('Hello %name%', [
'%name%' => 'Will'
]);
// French: Bonjour Will
// Default: Hello Will
To translate the message, Symfony uses a simple process:
The locale of the current user, which is stored on the request (or stored as
_locale on the session), is determined;
A catalog of translated messages is loaded from translation resources defined
for the locale (e.g. fr_FR). Messages from the fallback locale are also loaded
and added to the catalog if they don't already exist. The end result is a large
"dictionary" of translations;
If the message is located in the catalog, the translation is returned. If not, the translator returns the original message.
When using the trans() method, Symfony looks for the exact string inside the
appropriate message catalog and returns it (if it exists).
Symfony looks for message files (i.e. translations) in the following locations:
<kernel root directory>/Resources/translations directory;<kernel root directory>/Resources/<bundle name>/translations directory;Resources/translations/ directory of the bundle.The filename of the translations is also important as Symfony uses a convention
to determine details about the translations. Each message file must be named
according to the following path: domain.locale.loader:
domain: an optional way to organize messages into groups;locale: the locale that the translations are for (en_GB, en, etc);loader: how Symfony should load and parse the file (xliff, php or yml).When a translation has different forms due to pluralization, you can provide all
the forms as a string separated by a pipe (|):
'There is one apple|There are %count% apples'
To translate pluralized messages, use the transChoice() method:
$t = $this->get('translator')->transChoice(
'There is one apple|There are %count% apples',
10,
array('%count%' => 10)
);
The second argument (10 in this example), is the number of objects being
described and is used to determine which translation to use and also to
populate the %count% placeholder.
Sometimes, you'll need more control or want a different translation for specific cases (for 0, or when the count is negative, for example). For such cases, you can use explicit math intervals:
'{0} There are no apples|{1} There is one apple|]1,19] There are
%count% apples|[20,Inf[ There are many apples'
The intervals follow the ISO 31-11 notation.
An Interval can represent a finite set of numbers:
{1,2,3,4}
Or numbers between two other numbers:
[1, +Inf[
]-1,2[
# app/Resources/translations/Hello.fr.yml
ba:
bar: Bonjour.
place.holder: Bonjour %username%!
plural: Il y a %count% pomme|Il y a %count% pommes
<script src="{{ url('bazinga_jstranslation_js', { 'domain': 'Hello' }) }}">
</script>
A Translator object is now available in your JavaScript:
Translator.trans('ba.bar', {}, 'Hello', 'fr');
// "Bonjour."
Translator.trans('place.holder', { "username" : "Will" }, 'Hello');
// "Bonjour Will!"
Translator.transChoice('plural', 1, { "count": 1 }, 'Hello');
// "Il y a 1 pomme"
Translator.transChoice('plural', 10, { "count": 10 }, 'Hello');
// "Il y a 10 pommes"
The nature of rich web applications means that they're dynamic. No matter how efficient your application, each request will always contain more overhead than serving a static file.
But as your site grows, that overhead can become a problem. The processing that's normally performed on every request should be done only once.
This is exactly what caching aims to accomplish!
A gateway cache, or reverse proxy, is an independent layer that sits in front of your application.
The reverse proxy caches responses as they are returned from your application and answers requests with cached responses before they hit your application.
Symfony provides its own reverse proxy, but any reverse proxy can be used.
HTTP cache headers are used to communicate with the gateway cache and any other caches between your application and the client.
Symfony provides sensible defaults and a powerful interface for interacting with the cache headers.
HTTP expiration and validation are the two models used for determining whether cached content is fresh (can be reused from the cache) or stale (should be regenerated by the application).
Edge Side Includes (ESI) allow HTTP cache to be used to cache page fragments (even nested fragments) independently. With ESI, you can even cache an entire page for 60 minutes, but an embedded sidebar for only 5 minutes.
When caching with HTTP, the cache is separated from your application entirely and sits between your application and the client making the request.
The job of the cache is to accept requests from the client and pass them back to your application.
The cache will also receive responses back from your application and forward them on to the client. The cache is the middle-man of the request-response communication between the client and your application.
Along the way, the cache will store each response that is deemed cacheable. If the same resource is requested again, the cache sends the cached response to the client, ignoring your application entirely.
This type of cache is known as a HTTP gateway cache and many exist such as Varnish, Squid in reverse proxy mode, and the Symfony reverse proxy.
The HTTP cache headers sent by your application are consumed and interpreted by up to three different types of caches:
HTTP specifies four response cache headers that are looked at here:
Cache-ControlExpiresETagLast-ModifiedBoth gateway and proxy caches are considered shared caches as the cached content is shared by more than one user.
If a user-specific response were ever mistakenly stored by a shared cache, it might be returned later to any number of different users. Imagine if your account information were cached and then returned to every subsequent user who asked for their account page!
To handle this situation, every response may be set to be public or private:
HTTP caching only works for safe HTTP methods (like GET and HEAD). Being
safe means that you never change the application's state on the server when
serving the request.
This has two very reasonable consequences:
GET or HEAD request. Even if you don't use a gateway cache, the presence
of proxy caches mean that any GET or HEAD request may or may not actually
hit your server;PUT, POST or DELETE methods to cache. These methods are
meant to be used when mutating the state of your application. Caching them
would prevent certain requests from hitting and mutating your application.The expiration model is the more efficient and straightforward of the two caching models and should be used whenever possible.
When a response is cached with an expiration, the cache will store the response and return it directly without hitting the application until it expires.
The expiration model can be accomplished using one of two HTTP headers:
Cache-ControlExpiresThe Cache-Control header is unique in that it contains not one, but
various pieces of information about the cacheability of a response.
Each piece of information is separated by a comma:
Cache-Control: private, max-age=0, must-revalidate
Cache-Control: max-age=3600, must-revalidate
Symfony provides an abstraction around the Cache-Control header:
use Symfony\Component\HttpFoundation\Response;
$response = new Response();
// mark the response as either public or private
$response->setPublic();
$response->setPrivate();
// set the private or shared max age
$response->setMaxAge(600);
$response->setSharedMaxAge(600);
// set a custom Cache-Control directive
$response->headers->addCacheControlDirective('must-revalidate', true);
The Expires header can be set with the setExpires() Response method. It takes
a DateTime instance as an argument:
$date = new DateTime();
$date->modify('+600 seconds');
$response->setExpires($date);
The resulting HTTP header will look like this:
Expires: Thu, 01 Mar 2013 10:00:00 GMT
The setExpires() method automatically converts the date to the GMT timezone as
required by the specification.
With the expiration model, the application won't be asked to return the updated response until the cache finally becomes stale. It is not good!
The validation model addresses this issue.
Under this model, the cache continues to store responses. The difference is that, for each request, the cache asks the application whether or not the cached response is still valid.
If the cache is still valid, your application should return a 304 status code
and no content. This tells the cache that it's ok to return the cached response.
The ETag header is a string header called entity-tag that uniquely
identifies one representation of the target resource. It's entirely
generated and set by your application.
ETags are similar to fingerprints and they can be quickly compared to
determine if two versions of a resource are the same or not.
public function indexAction()
{
$response = $this->render('main/index.html.twig');
$response->setETag(md5($response->getContent()));
$response->setPublic(); // make sure the response is public/cacheable
$response->isNotModified($this->getRequest());
return $response;
}
The isNotModified() method compares the ETag sent with the Request with
the one set on the Response. If the two match, the method automatically sets
the Response status code to 304 Not Modified.
According to the HTTP specification, the Last-Modified header field indicates the date and time at which the origin server believes the representation was last modified.
In other words, the application decides whether or not the cached content has been updated based on whether or not it's been updated since the response was cached.
public function showAction($articleSlug)
{
// ...
$articleDate = new \DateTime($article->getUpdatedAt());
$authorDate = new \DateTime($author->getUpdatedAt());
$date = $authorDate > $articleDate ? $authorDate : $articleDate;
$response->setLastModified($date);
// Set response as public. Otherwise it will be private by default
$response->setPublic();
if ($response->isNotModified($this->getRequest())) {
return $response;
}
// ... do more work to populate the response
// with the full content
return $response;
}
Edge Side Includes or ESI is a small markup language for dynamic web content assembly at the reverse proxy level. The reverse proxy analyses the HTML code, parses ESI specific markup and assembles the final result before flushing it to the client.
<esi:include src="user.php" />
A convention for composing HttpKernelInterface middlewares:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class MyStackMiddleware implements HttpKernelInterface
{
private $app;
public function __construct(HttpKernelInterface $app)
{
$this->app = $app;
}
/**
* {@inheritDoc}
*/
public function handle(
Request $request,
$type = HttpKernelInterface::MASTER_REQUEST,
$catch = true
) {
// do something awesome
return $this->app->handle($request, $type, $catch);
}
}
Hack is a programming language for HHVM that interoperates seamlessly with PHP. It has been created by Facebook. In general, anything you can write in PHP, you can also write in Hack.
Hack reconciles the fast development cycle of PHP with the discipline provided by static typing, while adding many features commonly found in other modern programming languages such as generics, collections, and nullable.
It also provides built-in asynchronous programming.
Official website: hacklang.org
Use <?hh at the top of your file; you can also change <?php to <?hh in
existing PHP files, and your code will run just as before in HHVM.
Optionally name your file with the .hh extension to distinguish your Hack files from your PHP files. Of course, you can keep the name of the file .php (or any other extension that you use).
Important: Hack and HTML code do not mix.
<?hh
class MyClass {
public function hello(): string {
return 'Hello, World!';
}
}
function f(MyClass $m): string {
return $m->hello();
}
<?hh
class AnnotatedClass {
public int $x;
private string $s;
protected array $arr;
public AnotherClass $ac;
public function bar(string $str, bool $b): float {
if ($b && $str === "Hi") {
return 3.2;
}
return 0.3;
}
}
<?hh
class Box<T> {
public T $value;
public function __construct(T $v) {
$this->value = $v;
}
}
Hack provides a unified collections framework including: Vector, Map, Set, Pair.
Nullable allows any type to have null assigned and checked on it:
<?hh
function check_not_null(?int $x): int {
if ($x === null) {
return -1;
} else {
return $x;
}
}
Instead of writing a console application by hand, let's use an existing library: the Symfony2 Console component:
{
"require": {
"symfony/console": "~2.4"
}
}
The structure of your application should look like:
console-app
├── app
│  └── console
├── composer.json
├── src
├── tests
└── vendor
The easiest way to write strong console applications:
Your Commands should extend the Command class:
class GreetCommand extends Command
{
protected function configure()
{
// configure the name, arguments, options, etc.
}
protected function execute(InputInterface $in, OutputInterface $out) {
// do greet
}
}
$this
->setName('demo:greet')
->addArgument(
'name',
InputArgument::OPTIONAL,
'Who do you want to greet?'
);
if (null === $name = $input->getArgument('name')) {
$name = 'World';
}
$output->writeln('Hello, ' . $name);
#!/usr/bin/env php
# app/console
<?php
// require the Composer autoloader
require __DIR__ . '/../vendor/autoload.php';
$application = new Application();
$application->add(new GreetCommand());
$application->run();
$ app/console demo:greet
Hello, World
$ app/console demo:greet William
Hello, William
Read more: http://symfony.com/doc/current/components/console/.
<?php
namespace Vendor\Model;
class Foo
{
const VERSION = '1.0';
public $bar = null;
protected $opts;
private $var3 = 123;
public function __construct(BarInterface $bar, array $opts = [])
{
$this->bar = $bar;
$this->opts = $opts;
}
}
The PHP Coding Standards Fixer tool fixes most issues in your code when you want to follow the PHP coding standards as defined in the PSR-1 and PSR-2 documents.
$ wget http://cs.sensiolabs.org/get/php-cs-fixer.phar
$ php php-cs-fixer.phar fix /path/to/dir/or/file
More information at: http://cs.sensiolabs.org/.
Reduces dependency on implementation specifics and makes code more reusable.
The BananaController can use either Twig or the raw PHP implementation
as template engine thanks to the TemplateEngine interface:
interface TemplateEngine
{
/**
* @param string $template
* @param array $parameters
*
* @return string
*/
public function render($template, array $parameters = []);
}
You should think about interfaces, not about internal implementation details.
The Dependency Inversion Principle has two parts:
DIP is about the level of the abstraction in the messages sent from your code to the thing it is calling.
Dependency Injection is about how one object acquires a dependency.
class Foo
{
private $bar;
// **NOT** DI
public function __construct()
{
$this->bar = new Bar();
}
}
When a dependency is provided externally, then the system is using DI:
// DI!
public function __construct(Bar $bar)
{
$this->bar = $bar;
}
The basic idea behind a service locator is to have an object that knows how to get hold of all of the services that an application might need.
class ServiceLocator
{
public static function getBar()
{
return new Bar();
}
}
class Foo
{
private $bar;
public function __construct()
{
$this->bar = ServiceLocator::getBar();
}
}
All dependencies are injected using a constructor:
class Foo
{
private $bar;
public function __construct(BarInterface $bar)
{
$this->bar = $bar;
}
}
Dependencies are injected through setters:
class Foo
{
private $bar;
public function setBar(BarInterface $bar)
{
$this->bar = $bar;
}
}
An interface describes the injection:
interface BarAware
{
public function setBar(BarInterface $bar);
}
It needs to be implemented by the class that wants to use a BarInterface:
class Foo implements BarAware
{
private $bar;
public function setBar(BarInterface $bar)
{
$this->bar = $bar;
}
}
Inversion of Control is about who initiates the call. If your code initiates a call, it is not IoC, if the container/system/library calls back into code that you provided it, it is IoC.
Hollywood Principle: Don't call us, we'll call you.
A framework or library for building graphs of objects by passing in (injecting) each object's dependencies. Object lifetimes are handled by the container instead of by the consuming object.
Most of the time, you rely on configuration files to describe your classes and their dependencies. A class in this context is also known as a service.
You ask this container to retrieve a service, and it is lazy loaded and dynamically built:
// It's an instance of `TemplateEngine`, but you don't know
// anything about its internal implementation.
// Is it the raw PHP implementation or Twig?
$engine = $container->get('template_engine');
Twittee is the smallest Dependency Injection Container written in PHP. It fits in a tweet (less than 140 characters):
class Container
{
protected $s = array();
function __set($k, $c)
{
$this->s[$k] = $c;
}
function __get($k)
{
return $this->s[$k]($this);
}
}
Pimple is a small Dependency Injection Container for PHP 5.3 that consists of just one file and one class.
The DependencyInjection component allows you to standardize and centralize the way objects are constructed in your application.
Read more:
It’s all about Separation of Concerns (SoC).
You design components with their own logic, each component does one thing well, and only one thing.
How to manage these components in your application?
Read more: http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/#stupid-code-seriously.
Read more: http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/#solid-to-the-rescue.
9 rules invented by Jeff Bay in his book The ThoughWorks Anthology:
Read more: http://williamdurand.fr/2013/06/03/object-calisthenics/.
I don't need tests: tests are
for people who write bugs.
— Hipster Hacker (@hipsterhacker) November 1,
2013
Unit testing is a Method by which individual units of source code are tested to determine if they are fit for use.
PHPUnit is the de-facto standard for unit testing in PHP projects.
Install it using Composer:
{
"require-dev": {
"phpunit/phpunit": "~3.7"
}
}
Or as a PHAR:
$ wget http://pear.phpunit.de/get/phpunit.phar
$ chmod +x phpunit.phar
The tests for a class Class go into a class ClassTest.
ClassTest should inherit from PHPUnit_Framework_TestCase, but
a common practice is to create a TestCase class for a project, and to inherit
from it:
class TestCase extends PHPUnit_Framework_TestCase {}
The tests are public methods that are named test*, but you can also use the
@test annotation:
class ClassTest extends TestCase
{
public function testFoo()
{
$object = new MyClass();
$this->assertEquals('foo', $object->foo(), 'optional comment');
}
}
assertEquals()assertTrue()assertFalse()See all assertion methods: http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html.
Running the test suite:
$ phpunit
PHPUnit 3.7.0 by Sebastian Bergmann.
.
Time: 1 seconds, Memory: 5.25Mb
OK (1 test, 1 assertion)
Getting the code coverage:
$ phpunit --coverage-text
See also:
$ phpunit --log-junit junit_output.xml
$ phpunit --testdox
Install Behat via Composer:
{
"require-dev": {
"behat/behat": "2.4.*@stable"
},
"config": {
"bin-dir": "bin/"
}
}
The bin/behat command is now installed!
Or as a PHAR:
$ wget https://github.com/downloads/Behat/Behat/behat.phar
$ chmod +x behat.phar
Initialize your project:
$ bin/behat --init
Define a Feature:
# features/your_first_feature.feature
Feature: Your first feature
In order to start using Behat
As a manager or developer
I need to try
Define a Scenario:
Scenario: Successfully describing scenario
Given there is something
When I do something
Then I should see something
Executing Behat:
$ behat
Writing your Step definitions:
/**
* @Given /^there is something$/
*/
public function thereIsSomething()
{
throw new PendingException();
}
Must Read: https://speakerdeck.com/everzet/behat-by-example.
Read more about Behat: http://docs.behat.org/.
function tweetest($c,$m) {$c=is_callable($c)?$c():$c;echo($c)?'.':"F[{$m}]";}
tweetest($testValue !== 'bar', '$testValue should never equal bar');
function it($m,$p){echo ($p?'✔︎':'✘')." It $m\n";if(!$p){$GLOBALS['f']=1;}}
function done(){if(@$GLOBALS['f'])die(1);}
it("should sum two numbers", 1 + 1 == 2);
it("should display an X for a failing test", 1 + 1 == 3);
done();
Assertions and guard methods for input validation (not filtering!) in business-model, libraries and application low-level code.
use Assert\Assertion;
use Assert\AssertionFailedException;
try {
\Assert\that($identifier)->notEmpty()->integer();
Assertion::notEmpty($message, 'Message is not specified');
} catch(AssertionFailedException $e) {
$e->getValue(); // the value that caused the failure
$e->getConstraints(); // the additional constraints of the assertion
}
A simple API extension for DateTime.
$tomorrow = Carbon::now()->addDay();
$lastWeek = (new Carbon())->subWeek();
$noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London');
if (Carbon::now()->isWeekend()) {
echo 'Party!';
}
Carbon::now()->subDays(5)->diffForHumans(); // 5 days ago
Freezing time:
Carbon::setTestNow(Carbon::create(2001, 5, 21, 12));
echo Carbon::now(); // 2001-05-21 12:00:00
// test, test, test!
Carbon::setTestNow(); // clear the mock
echo Carbon::now(); // 2014-02-02 21:00:00
Fake data generator.
// use the factory to create a `Faker\Generator` instance
$faker = Faker\Factory::create();
// generate data by accessing properties
echo $faker->name;
// 'Lucy Cechtelar';
echo $faker->address;
// "426 Jordy Lodge
// Cartwrightshire, SC 88120-6700"
echo $faker->text;
// Sint velit eveniet. Rerum atque repellat voluptatem quia rerum. Numquam
// beatae sint laudantium consequatur. Magni occaecati itaque sint et sit
// tempore. Nesciunt amet quidem. Iusto deleniti cum autem ad quia aperiam.
echo $faker->email;
// 'tkshlerin@collins.com'
echo $faker->ipv4;
// '109.133.32.252'
echo $faker->creditCardNumber;
// '4485480221084675'
Filesystem abstraction layer.
use League\Flysystem as F
// Adapters: Local, Amazon Web Services - S3, Rackspace Cloud Files,
// Dropbox, Ftp, Sftp, Zip, WebDAV
$filesystem = new F\Filesystem(new F\Adapter\Local(__DIR__.'/path/to/dir'));
// Create a file
$filesystem->write('path/to/file.txt', 'contents');
// Update a file
$filesystem->update('file/to/update.ext', 'new contents');
// Write or update a file
$filesystem->put('filename.txt', 'contents');
// Delete a file
$filesyste->delete('delete/this/file.md');
// Check if a file exists
$exists = $filesystem->has('filename.txt');
// The PHPUnit Way
$mock = $this->getMock('SomeClass');
$mock->expects($this->once())
->method('getName')
->will($this->returnValue('John Doe'));
$mock2 = $this->getMock('AnotherClass');
$mock2->expects($this->any())
->method('getNumber')
->with(2)
->will($this->returnValue(2));
$mock2->expects($this->any())
->method('getNumber')
->with(3)
->will($this->returnValue(3));
// The Mockery Way
$mock = \Mockery::mock('SomeClass');
$mock->shouldReceive('getName')->once()->andReturn('John Doe');
$mock2 = \Mockery::mock('AnotherClass');
$mock2->shouldReceive('getNumber')->with(2)->andReturn(2);
$mock2->shouldReceive('getNumber')->with(3)->andReturn(3);
use PhpOption\Option;
class MyRepository
{
public function findSomeEntity($criteria)
{
return Option::fromValue($this->em->find(...));
}
}
// returns entity, or throws exception
$entity = $repository->findSomeEntity(...)->get();
$entity = $repository->findSomeEntity(...)->getOrElse(new Entity());
A PHP parser written in PHP, producing Abstract Syntax Trees (AST).
<?php
echo 'Hi', 'World';
array(
0: Stmt_Echo(
exprs: array(
0: Scalar_String(
value: Hi
)
1:
Scalar_String(
value:
World
)
)
)
)
Event-driven, non-blocking I/O with PHP: http://reactphp.org/.
Comprehensive mailing tools for PHP.
// Create the message
$message = Swift_Message::newInstance()
// Give the message a subject
->setSubject('Your subject')
// Set the From address with an associative array
->setFrom([ 'john@doe.com' => 'John Doe' ])
// Set the To addresses with an associative array
->setTo([ 'receiver@domain.org', 'other@domain.org' => 'A name' ])
// Give it a body
->setBody('Here is the message itself')
;
// Create the Transport
$transport = Swift_SmtpTransport::newInstance('smtp.example.org', 25);
// Create the Mailer using your created Transport
$mailer = Swift_Mailer::newInstance($transport);
// Send the message
$result = $mailer->send($message);
PHP errors for cool kids.
PHP Back-end Developper @ Clever-Age
Cursus:
Anciens employeurs:
Passions: Initiateur / Entraineur Apnée niveau 2 (Paris XV) Roller (24h du Mans) * Cinéma
| Table of contents | t |
|---|---|
| Exposé | ESC |
| Autoscale | e |
| Full screen slides | f |
| Presenter view | p |
| Edit the current slide | s |
| Slide numbers | n |
| Blank screen | b |
| Notes | 2 |
| Help | h |